#include "iConfig.hpp"

#include <glf/core/infos.h>

#if GLF_USE_SVN_INFOS
//if this file doesnt exist, you need to run glf\setup.[bat|sh] on each machine that compiles glf
#include <../../glf_config/svn_infos.h>
#endif

#include <algorithm>
#include <string>
#include <stdlib.h>
#include <ctype.h>

#include <sstream>

#include <glf/io/fileStream.h>

namespace glf {

PropertyMap* PropertyMap::sThis = 0;

const char* const PropertyMap::PROP_HW_MACHINE			= "hw.machine";
const char* const PropertyMap::PROP_HW_MODEL			= "hw.model";
const char* const PropertyMap::PROP_HW_NCPU			= "hw.ncpu";
const char* const PropertyMap::PROP_HW_BYTEORDER		= "hw.byteOrder";
const char* const PropertyMap::PROP_HW_PHYSMEM			= "hw.physMem";
const char* const PropertyMap::PROP_HW_USERMEM			= "hw.userMem";
const char* const PropertyMap::PROP_HW_PAGESIZE		= "hw.pageSize";
const char* const PropertyMap::PROP_HW_FLOATINGPOINT	= "hw.floatingPoint";
const char* const PropertyMap::PROP_HW_MACHINE_ARCH	= "hw.machineArch";

const char* const PropertyMap::PROP_HW_VECTORUNIT	= "hw.vectorUnit";
const char* const PropertyMap::PROP_HW_BUS_FREQ		= "hw.busFreq";
const char* const PropertyMap::PROP_HW_CPU_FREQ		= "hw.cpuFreq";
const char* const PropertyMap::PROP_HW_CACHELINE	= "hw.cacheLine";
const char* const PropertyMap::PROP_HW_L1ICACHESIZE	= "hw.l1iCacheSize";
const char* const PropertyMap::PROP_HW_L1DCACHESIZE	= "hw.l1dCacheSize";
const char* const PropertyMap::PROP_HW_L2SETTINGS	= "hw.l2settings";
const char* const PropertyMap::PROP_HW_L2CACHESIZE	= "hw.l2cacheSize";
const char* const PropertyMap::PROP_HW_L3SETTINGS	= "hw.l3settings";
const char* const PropertyMap::PROP_HW_L3CACHESIZE	= "hw.l3cacheSize";
const char* const PropertyMap::PROP_HW_TB_FREQ		= "hw.tbFreq";
const char* const PropertyMap::PROP_HW_MEMSIZE		= "hw.memSize";	
const char* const PropertyMap::PROP_HW_AVAILCPU		= "hw.availCpu";
const char* const PropertyMap::PROP_HW_MAXID		= "hw.maxId";

const char* const PropertyMap::PROP_SVN_GLF_URL		= "svn.glf.url";
const char* const PropertyMap::PROP_SVN_GLF_REVISION	= "svn.glf.rev";

const char* const PropertyMap::PROP_SVN_GLITCH_URL		= "svn.glitch.url";
const char* const PropertyMap::PROP_SVN_GLITCH_REVISION= "svn.glitch.rev";

const char* const PropertyMap::PROP_SVN_VOX_URL		= "svn.vox.url";
const char* const PropertyMap::PROP_SVN_VOX_REVISION	= "svn.vox.rev";

const char* const PropertyMap::PROP_SVN_SWF_URL		= "svn.swf.url";
const char* const PropertyMap::PROP_SVN_SWF_REVISION	= "svn.swf.rev";

void GetDiskInfo(uint64& totalDiskSpace, uint64& freeDiskSpace);

}

#ifdef WIN32

#include <windows.h>

namespace glf {

void GetPhysicalMemoryInfo( uint64& FreeMemory, uint64& UsedMemory ) {
}

void GetTaskMemoryInfo(uint64& ResidentSize, uint64& VirtualSize) {
}

void GetDiskInfo(uint64& totalDiskSpace, uint64& freeDiskSpace) {

}

void PropertyMap::SetDefaultProperties() { 
#if GLF_PLATFORM_WP8 == 0
	SYSTEM_INFO sysinfo;
	GetSystemInfo(&sysinfo);

	SetPropertyT(PROP_HW_NCPU, (int)sysinfo.dwNumberOfProcessors);
	SetPropertyT(PROP_HW_PAGESIZE, (int)sysinfo.dwPageSize);
#endif
}

}

#elif defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) //|| defined(__ANDROID__) //dont know if android have these headers/functions

#include <sys/param.h>
#include <mach/mach_host.h>
#include <mach/task.h>
#include <sys/sysctl.h>
#include <sys/types.h>

namespace glf {

struct CtlHw {
	const char* const propertyName;
	int levelName1;
	int levelName2;
	enum Type { Integer, Integer64, String } type;
} gCtlHwInfos[] = {
	{ PropertyMap::PROP_HW_MACHINE,			CTL_HW,	HW_MACHINE, CtlHw::String },
	{ PropertyMap::PROP_HW_MODEL,			CTL_HW,	HW_MODEL, CtlHw::String },
	{ PropertyMap::PROP_HW_NCPU,			CTL_HW,	HW_NCPU, CtlHw::Integer },
	{ PropertyMap::PROP_HW_BYTEORDER,		CTL_HW,	HW_BYTEORDER, CtlHw::Integer },
	{ PropertyMap::PROP_HW_PHYSMEM,			CTL_HW,	HW_PHYSMEM, CtlHw::Integer },
	{ PropertyMap::PROP_HW_USERMEM,			CTL_HW,	HW_USERMEM, CtlHw::Integer },
	{ PropertyMap::PROP_HW_PAGESIZE,		CTL_HW,	HW_PAGESIZE, CtlHw::Integer },
	//{ PropertyMap::PROP_HW_FLOATINGPOINT,	CTL_HW,	HW_FLOATINGPOINT, CtlHw::Integer },
	{ PropertyMap::PROP_HW_MACHINE_ARCH,	CTL_HW,	HW_MACHINE_ARCH, CtlHw::String },
	{ PropertyMap::PROP_HW_VECTORUNIT,		CTL_HW, HW_VECTORUNIT, CtlHw::Integer },
	{ PropertyMap::PROP_HW_BUS_FREQ,		CTL_HW, HW_BUS_FREQ, CtlHw::Integer },
	{ PropertyMap::PROP_HW_CPU_FREQ,		CTL_HW, HW_CPU_FREQ, CtlHw::Integer },
	{ PropertyMap::PROP_HW_CACHELINE,		CTL_HW, HW_CACHELINE, CtlHw::Integer },
	{ PropertyMap::PROP_HW_L1ICACHESIZE,	CTL_HW, HW_L1ICACHESIZE, CtlHw::Integer },
	{ PropertyMap::PROP_HW_L1DCACHESIZE,	CTL_HW, HW_L1DCACHESIZE, CtlHw::Integer },
	{ PropertyMap::PROP_HW_L2SETTINGS,		CTL_HW, HW_L2SETTINGS, CtlHw::Integer },
	{ PropertyMap::PROP_HW_L2CACHESIZE,		CTL_HW, HW_L2CACHESIZE, CtlHw::Integer },
	{ PropertyMap::PROP_HW_L3SETTINGS,		CTL_HW, HW_L3SETTINGS, CtlHw::Integer },
	{ PropertyMap::PROP_HW_L3CACHESIZE,		CTL_HW, HW_L3CACHESIZE, CtlHw::Integer },
	{ PropertyMap::PROP_HW_TB_FREQ,			CTL_HW, HW_TB_FREQ, CtlHw::Integer },
	{ PropertyMap::PROP_HW_MEMSIZE,			CTL_HW, HW_MEMSIZE, CtlHw::Integer64 },
	{ PropertyMap::PROP_HW_AVAILCPU,		CTL_HW, HW_AVAILCPU, CtlHw::Integer },
	{ PropertyMap::PROP_HW_MAXID,			CTL_HW, HW_MAXID, CtlHw::Integer },
};

void PropertyMap::SetDefaultProperties()
{
	for(size_t i = 0; i < GLF_ASIZEOF(gCtlHwInfos); ++i)
	{
		const CtlHw& ctlHw = gCtlHwInfos[i];

		int param[] = { ctlHw.levelName1, ctlHw.levelName2 };

		char buffer[1024] = {0};

		if(ctlHw.type == CtlHw::Integer)
		{
			int result;
			size_t len = sizeof(result);
			sysctl(param, 2, &result, &len, NULL, 0);
			
			SetPropertyT(ctlHw.propertyName, result);
			//sprintf(buffer, "%d", result);
		}
		else if(ctlHw.type == CtlHw::Integer64)
		{
			int64 result;
			size_t len = sizeof(result);
			sysctl(param, 2, &result, &len, NULL, 0);
			
			//sprintf(buffer, "%lld", result);
			SetPropertyT(ctlHw.propertyName, result);
		}		
		else if(ctlHw.type == CtlHw::String)
		{
			size_t len = sizeof(buffer);
			sysctl(param, 2, buffer, &len, NULL, 0);

			SetPropertyT(ctlHw.propertyName, buffer);
		}
		else
		{
			continue;
		}
	}
}

/*
	These are defined in mach/shared_memory_server.h, which does not exist when
	compiling for device 
*/
#ifndef SHARED_TEXT_REGION_SIZE
#define SHARED_TEXT_REGION_SIZE  0x08000000
#endif

#ifndef SHARED_DATA_REGION_SIZE
#define SHARED_DATA_REGION_SIZE 0x08000000
#endif

/**
 * Retrieve current memory information (for just this task)
 *
 * @param FreeMemory Amount of free memory in bytes
 * @param UsedMemory Amount of used memory in bytes
 */
void GetTaskMemoryInfo(uint64& ResidentSize, uint64& VirtualSize)
{
	// Get stats about the current task
	task_basic_info Stats;
	mach_msg_type_number_t Size = TASK_BASIC_INFO_COUNT;

	task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&Stats, &Size);

	// Return the most interesting stuff
	ResidentSize = Stats.resident_size;
	VirtualSize = Stats.virtual_size - SHARED_TEXT_REGION_SIZE - SHARED_DATA_REGION_SIZE;
}

/**
 * Retrieve current memory information (for the entire device, not limited to our process)
 *
 * @param FreeMemory Amount of free memory in bytes
 * @param UsedMemory Amount of used memory in bytes
 */
void GetPhysicalMemoryInfo( uint64& FreeMemory, uint64& UsedMemory )
{
	// get stats about the host (mem is in pages)
	vm_statistics Stats;
	mach_msg_type_number_t Size = sizeof(Stats);
	host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&Stats, &Size);
	
	// get the page size
	vm_size_t PageSize;
	host_page_size(mach_host_self(), &PageSize);
	
	// combine to get free memory!
	FreeMemory = Stats.free_count * PageSize;
	// and used memory 
	UsedMemory = ( Stats.active_count + Stats.inactive_count + Stats.wire_count ) * PageSize;
}
    
}

#else

namespace glf {

void GetPhysicalMemoryInfo( uint64& FreeMemory, uint64& UsedMemory ) {
}

void GetTaskMemoryInfo(uint64& ResidentSize, uint64& VirtualSize) {
}

void PropertyMap::SetDefaultProperties() {

}

void GetDiskInfo(uint64& totalDiskSpace, uint64& freeDiskSpace) {

}

}

#endif

namespace glf {

//#define DEVICE_PROPERTIES()					\
//	enum DeviceProperty {					\
//		ENUM_ELEM_CALL(ENUM_E_ENUM, Base, Class)\
//		COUNT,								\
//	};										\
//	const char* gDevicePropertyNames[] = {	\
//		ENUM_ELEM_CALL(ENUM_E_CTOR, Base, Class) \
//	};
//
//#define ENUM_ELEM_CALL(EFunc, Base, Class)          ENUM_LIST(EFunc, GLF_STRINGIFY_EVAL(GLF_IFEMPTY(, GLF_UNBRACKET(Base)::, Base)Class))
//
//#define ENUM_E_ENUM(...)                            GLF_EVAL(GLF_TOKENIZE_EVAL(ENUM_E_ENUM_, GLF_NUMARGS(__VA_ARGS__))(__VA_ARGS__))
//#define ENUM_E_ENUM_2(ClassId, name)                name, 
//#define ENUM_E_ENUM_4(ClassId, name, str, val)      name GLF_IFEMPTY(,= GLF_UNBRACKET(val),val), 
//
//#define ENUM_E_CTOR(...)                            GLF_EVAL(GLF_TOKENIZE_EVAL(ENUM_E_CTOR_, GLF_NUMARGS(__VA_ARGS__))(__VA_ARGS__))
//#define ENUM_E_CTOR_2(ClassId, name)                ENUM_E_CTOR_4(ClassId, name, GLF_EMPTY, GLF_EMPTY)
//#define ENUM_E_CTOR_4(ClassId, name, str, val)      #name,
//
//#define ENUM_LIST(e,_) e(_, HW_MACHINE) e(_, HW_MODEL)
//DEVICE_PROPERTIES()

std::string PropertyMap::Value::getAsString(bool withType) const 
{
	std::stringstream os;
	if(type == glf::PropertyMap::EVT_BOOL)
		os << (withType?"(bool) ":"") << valueBool ? "true" : "false";
	else if(type == glf::PropertyMap::EVT_INT32)
		os << (withType?"(int32) ":"") << valueInt32;
	else if(type == glf::PropertyMap::EVT_INT64)
		os << (withType?"(int64) ":"") << valueInt64;
	else if(type == glf::PropertyMap::EVT_FLOAT)
		os << (withType?"(float) ":"") << valueFloat;
	else if(type == glf::PropertyMap::EVT_STRING)
		os << (withType?"(string) ":"") << valueString;	
	return os.str();
}
	
std::ostream& operator<<(std::ostream& os, const glf::PropertyMap::Value& v)
{
	if(v.type == glf::PropertyMap::EVT_BOOL)
		os << "(bool) " << v.valueBool ? "true" : "false";
	else if(v.type == glf::PropertyMap::EVT_INT32)
		os << "(int32) " << v.valueInt32;
	else if(v.type == glf::PropertyMap::EVT_INT64)
		os << "(int64) " << v.valueInt64;
	else if(v.type == glf::PropertyMap::EVT_FLOAT)
		os << "(float) " << v.valueFloat;
	else if(v.type == glf::PropertyMap::EVT_STRING)
		os << "(string) " << v.valueString;	
	return os;
}

void GetRuntimeInfos(RuntimeInfos& infos) {
	GetTaskMemoryInfo(infos.taskResidentSize, infos.taskVirtualSize);
	GetPhysicalMemoryInfo(infos.freeMemory, infos.usedMemory);
	GetDiskInfo(infos.totalDiskSpace, infos.freeDiskSpace);
}

PropertyMap::PropertyMap()
:	mLastLogHasBeenLoaded(false)
{
	GLF_ASSERT(sThis == 0);
	sThis = this;
	mIsLogging = false;

	SetDefaultProperties();

#if GLF_USE_SVN_INFOS
	for(size_t i = 0; i < GLF_ASIZEOF(sSvnLibFolderInfos); ++i) {
		const SvnLibFolderInfos& info = sSvnLibFolderInfos[i];

		char buffer[1024];

		sprintf(buffer, "svn.%s.url", info.folder);
		std::transform(buffer, buffer + strlen(buffer), buffer, ::tolower);
		SetProperty(buffer, info.url);

		sprintf(buffer, "svn.%s.revision", info.folder);
		std::transform(buffer, buffer + strlen(buffer), buffer, ::tolower);
		SetProperty(buffer, info.revision);
	}
#endif
}

void PropertyMap::SetProperty(const key_t& key, const char* value, int flags) {

	Value v;
	
	v.type = EVT_STRING;
	if(value)
	{
		v.valueString = value;
	
		SetProperty(key, v, flags);
	}
}

void PropertyMap::SetProperty(const key_t& key, const std::string& value, int flags) {
	Value v;
	
	v.type = EVT_STRING;
	v.valueString = value;
	
	SetProperty(key, v, flags);
}

void PropertyMap::SetProperty(const key_t& key, Value& v, int flags) {
	if(mIsLogging)
	{
		if(v.type == EVT_INT32) {
			Console::Println("SetProperty %s -> %d", key.c_str(), v.valueInt32);
		} else if(v.type == EVT_INT64) {
			Console::Println("SetProperty %s -> %lld", key.c_str(), v.valueInt64);
		} else if(v.type == EVT_BOOL) {
			Console::Println("SetProperty %s -> %s", key.c_str(), v.valueBool ? "true" : "false");
		} else if(v.type == EVT_FLOAT) {
			Console::Println("SetProperty %s -> %f", key.c_str(), v.valueFloat);
		} else if(v.type == EVT_STRING) {
			Console::Println("SetProperty %s -> %s", key.c_str(), v.valueString.c_str());
		}
	}
	
	v.flags = flags;
	map_t::iterator it = mMap.find(key);
	if(it == mMap.end()) {
		mMap.insert(std::make_pair(key, v));
	} else {
		v.flags = it->second.flags;
		it->second = v;
	}

	if(v.IsPersistant())
		SavePersistant();
}

const PropertyMap::map_t& PropertyMap::GetProperties() const {
	return mMap;
}

bool PropertyMap::HasProperty(const key_t& key) const {
	return mMap.find(key) != mMap.end();
}

void PropertyMap::SetPersistant(const key_t& key) {

	map_t::iterator it = mMap.find(key);
	if(it == mMap.end())
		return;
	Value& v = it->second;
	v.SetFlag(EF_PERSISTANT);
	SavePersistant();
}

const std::string& PropertyMap::GetProperty(const key_t& key) const {
	map_t::const_iterator it = mMap.find(key);
	if(it == mMap.end())
		return mEmpty;
	const Value& v = it->second;
	if(v.type == EVT_STRING)
		return v.valueString;
	return mEmpty;
}

PropertyMap::Result PropertyMap::GetPropertyEx(const key_t& key, ValueType vt) const {
	map_t::const_iterator it = mMap.find(key);
	Result r = { Result::OK };
	
	if(it == mMap.end()) 
	{
		r.status = Result::MISSING;
	} 
	else 
	{
		r.value = it->second;
		if(vt == EVT_ANY)
		{
			r.status = Result::OK;
		}
		else
		{
			r.status = vt == r.value.type ? Result::OK : Result::WRONG_TYPE;
		}
	}
	return r;
}

void PropertyMap::PrintAll() const {
	for(map_t::const_iterator it = mMap.begin(); it != mMap.end(); ++it) {
		const char* key = it->first.c_str();
		const Value& v = it->second;
		if(v.type == EVT_INT32) {
			Console::Println("%s(int32)-> %d", key, v.valueInt32);
		} else if(v.type == EVT_INT64) {
			Console::Println("%s(int64) -> %lld", key, v.valueInt64);
		} else if(v.type == EVT_BOOL) {
			Console::Println("%s(bool) -> %s", key, v.valueBool ? "true" : "false");
		} else if(v.type == EVT_FLOAT) {
			Console::Println("%s(float) -> %f", key, v.valueFloat);
		} else if(v.type == EVT_STRING) {
			Console::Println("%s(string) -> %s", key, v.valueString.c_str());
		}
	}
}

const char* PropertyMap::GetTypeName(ValueType vt) {
	const char* sValueTypeName[EVT_COUNT+1] = {
		"any",
		"int32",
		"int64",
		"float",
		"bool",
		"string",
		"invalid",
	};
	
	GLF_ASSERT(vt >= 0 && vt <EVT_COUNT);
	return sValueTypeName[vt];
}

PropertyMap::ValueType PropertyMap::GetTypeFromName(const char* name) {
	for(int i = 0; i < EVT_COUNT; ++i) {
		if(strcmp(GetTypeName(ValueType(i)), name) == 0) {
			return ValueType(i);
		}
		
	}
	return EVT_COUNT;
}

void PropertyMap::SavePersistant() {
	if(!mLastLogHasBeenLoaded)
		return;

	FileStream fs("properties.bin", ios::loc_home|ios::create|ios::trunc|ios::write);

	if(fs.IsOpened()) {
		for(map_t::const_iterator it = mMap.begin(); it != mMap.end(); ++it) {
			const char* key = it->first.c_str();
			const Value& v = it->second;
			if(v.IsPersistant()) {
				std::string valueAsString = v.getAsString();

				int flags = v.flags & ~EF_PERSISTANT;

				std::stringstream ss;
				ss << key << " " << v.GetTypeName() << " " << flags << " " << valueAsString.c_str()  << "\n";
				std::string towrite = ss.str();
				fs.Write(towrite.c_str(), towrite.size()); 
			}
		}
	}
}

void PropertyMap::LoadLastPersistant() {
	return;	//ignore for now

	FileStream fs("properties.bin", ios::loc_home|ios::read);

	if(fs.IsOpened() && fs.GetSize() > 0) {
		int size = fs.GetSize();
		std::vector<char> buf(size);
		fs.Read(&buf[0], size);
		buf.push_back(0);
		std::string s = &buf[0];

		std::string::size_type offset = 0;
		while(true) {
			std::string::size_type eol = s.find("\n", offset);
			
			if(eol == std::string::npos) {
				break;
			}

			std::string line(&s[offset], eol - offset);

			std::string::size_type space1 = line.find(" ");
			if(space1 != std::string::npos) {
				std::string::size_type space2 = line.find(" ", space1 + 1);
				if(space2 != std::string::npos) {
					std::string::size_type space3 = line.find(" ", space2 + 1);
					if(space3 != std::string::npos) {
						std::string key = std::string("last.") + std::string(line, 0, space1);
						std::string type(line, space1+1, space2 - space1);
						std::string flags(line, space2+1, space3 - space2);
						std::string value(line, space3);
						
						Value v = Value::FromString(type.c_str(), value.c_str());
						v.flags = atoi(flags.c_str());
	
						mMap.insert(std::make_pair(key, v));
					}
				}
			}

			offset = eol + 1;
		}
	}

	mLastLogHasBeenLoaded = true;
}

PropertyMap::Value PropertyMap::Value::FromString(const char* type, const char* value) {
	switch(GetTypeFromName(type)) {
		case EVT_INT32: 
		{
			int32 i;
			sscanf(value, "%d", &i);
			return Value((int32)i);
		}
		case EVT_INT64:
		{
			int64 i;
			sscanf(value, "%lld", &i);
			return Value(i);
		}
		case EVT_FLOAT:
		{
			float f;
			sscanf(value, "%f", &f);
			return Value(f);
		}
		case EVT_STRING:
			return Value(value);
		case EVT_BOOL:
		{
			if(strcmp(value, "false") == 0)
				return Value(false);
			else if(strcmp(value, "true") == 0)
				return Value(true);
		}
	}
	return Value();
}

}